/**
 * OSS is a Open Service Container which gives the leverage of building the Desktop and Web
 * Application. * Copyright (C) 2008-2009  Ponraj Sutanthiramani
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Or5e Open Source Community
 * 105, S.S Paradise Apartment, Munnireddy Layout, Horamavu, Banaswadi, Bangalore, India.
 */
package org.or5e.api.webcam.video;

import java.awt.*;
import javax.media.*;
import javax.media.control.TrackControl;
import javax.media.Format;
import javax.media.format.*;
import org.or5e.api.webcam.util.ElapsedTimer;

public class FrameAccess extends Frame implements ControllerListener {

	private static final long serialVersionUID = 1L;
	Processor p;
    Object waitSync = new Object();
    boolean stateTransitionOK = true;
    Label summary;
    static ShortFrameProcessor proc = new FrameDifference();
    // static ShortFrameProcessor proc = new FrameSaver();
    // static ShortFrameProcessor proc = new FrameThreshold();

    public boolean open(MediaLocator ml) {

        try {
            p = Manager.createProcessor(ml);
        } catch (Exception e) {
            System.err.println("Failed to create a processor from the given url: " + e);
            return false;
        }

        p.addControllerListener(this);

        // Put the Processor into configured state.
        p.configure();
        if (!waitForState(Processor.Configured)) {
            System.err.println("Failed to configure the processor.");
            return false;
        }

        // So I can use it as a player.
        p.setContentDescriptor(null);

        // Obtain the track controls.
        TrackControl tc[] = p.getTrackControls();

        if (tc == null) {
            System.err.println("Failed to obtain track controls from the processor.");
            return false;
        }

        // Search for the track control for the video track.
        TrackControl videoTrack = null;

        for (int i = 0; i < tc.length; i++) {
            if (tc[i].getFormat() instanceof VideoFormat) {
                videoTrack = tc[i];
                System.out.println("VideoTrack Format: " + videoTrack.getFormat());
                break;
            }
        }

        if (videoTrack == null) {
            System.err.println("The input media does not contain a video track.");
            return false;
        }

        System.err.println("Video format: " + videoTrack.getFormat());

        // Instantiate and set the frame access codec to the data flow path.
        try {
            Codec codec[] = {// new PreAccessCodec(),
                new PostAccessCodec()};
            videoTrack.setCodecChain(codec);
        } catch (UnsupportedPlugInException e) {
            System.err.println("The process does not support effects.");
        }

        // Realize the processor.
        p.prefetch();
        if (!waitForState(Processor.Prefetched)) {
            System.err.println("Failed to realize the processor.");
            return false;
        }

        // Display the visual & control component if there's one.

        setLayout(new BorderLayout());
        Component cc;
        Component vc;

        if ((vc = p.getVisualComponent()) != null) {
            add("Center", vc);
        }

        if ((cc = p.getControlPanelComponent()) != null) {
            add("South", cc);
        }

        summary = new Label("r = ???? ; g = ???? ; b = ????");
        add(BorderLayout.NORTH, summary);

        // Start the processor.
        p.start();
        setVisible(true);
        return true;
    }

    @Override
    public void addNotify() {
        super.addNotify();
        pack();
    }

    boolean waitForState(int state) {
        synchronized (waitSync) {
            try {
                while (p.getState() != state && stateTransitionOK) {
                    waitSync.wait();
                }
            } catch (Exception e) {
            }
        }
        return stateTransitionOK;
    }

    @Override
    public void controllerUpdate(ControllerEvent evt) {

        if (evt instanceof ConfigureCompleteEvent ||
                evt instanceof RealizeCompleteEvent ||
                evt instanceof PrefetchCompleteEvent) {
            synchronized (waitSync) {
                stateTransitionOK = true;
                waitSync.notifyAll();
            }
        } else if (evt instanceof ResourceUnavailableEvent) {
            synchronized (waitSync) {
                stateTransitionOK = false;
                waitSync.notifyAll();
            }
        } else if (evt instanceof EndOfMediaEvent) {
            p.close();
            System.exit(0);
        }
    }

    public static void main(String[] args) {
        short[] sa = new short[1];
        System.out.println("args: " + sa.getClass().getName());
        System.out.println(Short.MAX_VALUE + " : " + Short.MIN_VALUE);
        MediaLocator ml;

        //String str2 = "vfw:Microsoft WDM Image Capture (Win32):0";
        String str2 = "vfw:WebCam Vista/Live! Cam Chat (VFW):0";
        CaptureDeviceInfo di = CaptureDeviceManager.getDevice(str2);
        ml = di.getLocator();

        if ((ml == null)) { // new MediaLocator(url)) == null) {
            System.err.println("Cannot build media locator from: ");
            System.exit(0);
        }

        FrameAccess fa = new FrameAccess();
        if (!fa.open(ml)) {
            System.exit(0);
        }
    }

    static void prUsage() {
        System.err.println("Usage: java FrameAccess <url>");
    }
    public class PreAccessCodec implements Codec {
        ElapsedTimer timer = new ElapsedTimer();
        void accessFrame(Buffer frame) {
            timer.reset();
        }
        protected Format supportedIns[] = new Format[]{
            new VideoFormat(null)
        };
        protected Format supportedOuts[] = new Format[]{
            new VideoFormat(null)
        };
        Format input = null, output = null;
        @Override
        public String getName() {
            return "Pre-Access Codec";
        }
        @Override
        public void open() {
        }
        @Override
        public void close() {
        }
        @Override
        public void reset() {
        }
        @Override
        public Format[] getSupportedInputFormats() {
            return supportedIns;
        }
        @Override
        public Format[] getSupportedOutputFormats(Format in) {
            if (in == null) {
                return supportedOuts;
            } else {
                Format outs[] = new Format[1];
                outs[0] = in;
                return outs;
            }
        }
        @Override
        public Format setInputFormat(Format format) {
            input = format;
            return input;
        }
        @Override
        public Format setOutputFormat(Format format) {
            output = format;
            return output;
        }
        @Override
        public int process(Buffer in, Buffer out) {
            accessFrame(in);

            Object data = in.getData();
            in.setData(out.getData());
            out.setData(data);

            out.setFormat(in.getFormat());
            out.setLength(in.getLength());
            out.setOffset(in.getOffset());

            return BUFFER_PROCESSED_OK;
        }
        @Override
        public Object[] getControls() {
            return new Object[0];
        }
        @Override
        public Object getControl(String type) {
            return null;
        }
    }

    public class PostAccessCodec extends PreAccessCodec {
        ShortFrameProcessor proc;
        public PostAccessCodec() {
            supportedIns = new Format[]{new RGBFormat()};
            proc = FrameAccess.proc;
            proc.init(320, 240); // should get this from the video format...

        }
        @Override
        void accessFrame(Buffer frame) {
            byte[] frameData = (byte[]) frame.getData();
//            short[] shortFrm = new short[frameData.length];
//            int index = 0;
//            for (byte b : frameData) {
//                shortFrm[index++] = b;
//            }
            summary.setText(" : " + frame.getData().getClass());
            proc.process(frameData);
        }

        public String f(byte[] data) {
            double tot = 0.0;
            for (int i = 0; i < data.length; i++) {
                tot += b2int(data[i]);
            }
            return "" + (int) tot / data.length;
        }

        public int b2int(byte b) {
            return (256 + (int) b) % 256;
        }

        @Override
        public String getName() {
            return "Post-Access Codec";
        }
    }
}